package com.qianda.qdminiapp.service.client;

import java.io.Closeable;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.qianda.qdminiapp.common.util.HttpClientExecutor;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.message.BasicNameValuePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;



/**
 * 
 * @author gaigeshen
 * @since 12/27 2017
 */
public class ServiceClient implements Closeable {
  
  private static final Logger LOG = LoggerFactory.getLogger(ServiceClient.class);

  private static final Charset CHARSET = Charset.forName("utf-8");
  
  private final ServiceResultParser parser = new JSONServiceResultParser();

  private final Map<String, String> services;
  private final HttpClientExecutor executor;

  /**
   * 
   * @param hostAndPort
   * @param services
   * @param executor
   */
  public ServiceClient(String hostAndPort,
                       Map<String, String> services,
                       HttpClientExecutor executor) {
    LOG.info("Initialize service client");
    
    this.services = parseServicesUrl(hostAndPort, services);
    this.executor = executor;
    LOG.info("Service client initialized");
  }
  
  private String existingService(String name) throws NoSuchServiceException {
    String services = this.services.get(name);
    if (services == null) {
      throw new NoSuchServiceException(name);
    }
    return services;
  }
  
  @Override
  public void close() throws IOException {
    LOG.info("Close service client");
    executor.shutdown();
  }

  /**
   * 
   * @param hostAndPort
   * @param services
   * @return
   */
  private Map<String, String> parseServicesUrl(String hostAndPort, Map<String, String> services) {
    Map<String, String> result = new HashMap<>();
    if (services != null) {
      services.forEach((k, v) -> {
        if (k.endsWith("/")) {
          k = k.substring(0, k.length() - 1);
        }
        if (!v.startsWith("/")) {
          v = "/" + v;
        }
        result.put(k, hostAndPort + v);
      });
    }
    LOG.info("Services parsed: {}", result);
    return result;
  }

  /**
   * 
   * @param service 服务名称
   * @param dataType 数据类型
   * @return 服务结果数据
   * @throws NoSuchServiceException
   * @throws ServiceExecutionException
   * @throws ParseServiceResultException 
   */
  public <T> ServiceResult<T> get(
      String service,
      Class<T> dataType)
      throws NoSuchServiceException, ServiceExecutionException, ParseServiceResultException {
    
    return get(service, null, dataType);
    
  }

  /**
   * 
   * @param service 服务名称
   * @param params 参数
   * @param dataType 数据类型
   * @return 服务结果数据
   * @throws NoSuchServiceException
   * @throws ServiceExecutionException
   * @throws ParseServiceResultException 
   */
  public <T> ServiceResult<T> get(
      String service,
      Map<String, String> params,
      Class<T> dataType)
      throws NoSuchServiceException, ServiceExecutionException, ParseServiceResultException {

    String url = existingService(service);

    if (params != null) {
      int size = params.size();
      if (size != 0) {
        List<NameValuePair> pairs = new ArrayList<>(size);
        params.forEach((k, v) -> {
          pairs.add(new BasicNameValuePair(k, v));
        });

        String formated = URLEncodedUtils.format(pairs, CHARSET);
        url += "?";
        url += formated;
      }
    }

    HttpGet get = new HttpGet(url);
    return parseResult(get, dataType);
  }

  /**
   * 
   * @param service 服务名称
   * @param dataType 数据类型
   * @return 服务结果数据
   * @throws NoSuchServiceException
   * @throws ServiceExecutionException
   * @throws ParseServiceResultException 
   */
  public <T> ServiceResult<T> post(
      String service,
      Class<T> dataType)
      throws NoSuchServiceException, ServiceExecutionException, ParseServiceResultException {

    return post(service, null, dataType);

  }

  /**
   * 
   * 
   * @param service 服务名称
   * @param params 参数
   * @param dataType 数据类型
   * @return 服务结果数据
   * @throws NoSuchServiceException
   * @throws ServiceExecutionException
   * @throws ParseServiceResultException 
   */
  public <T> ServiceResult<T> post(
      String service,
      Map<String, String> params,
      Class<T> dataType)
      throws NoSuchServiceException, ServiceExecutionException, ParseServiceResultException {

    String url = existingService(service);

    HttpPost post = new HttpPost(url);

    if (params != null) {
      int size = params.size();
      if (size != 0) {
        List<NameValuePair> pairs = new ArrayList<>(size);
        params.forEach((k, v) -> {
          pairs.add(new BasicNameValuePair(k, v));
        });

        ContentType contentType = ContentType.create(URLEncodedUtils.CONTENT_TYPE, CHARSET);

        String formated = URLEncodedUtils.format(pairs, "utf-8");
        post.setEntity(new StringEntity(formated, contentType));
      }
    }

    return parseResult(post, dataType);
  }

  /**
   * 
   * @param req
   * @param dataType
   * @return
   * @throws ServiceExecutionException
   * @throws ParseServiceResultException 
   */
  private <T> ServiceResult<T> parseResult(
      HttpUriRequest req,
      Class<T> dataType)
      throws ServiceExecutionException, ParseServiceResultException {

    String result = null;
    try {
      result = executor.execute(req);
    } catch (Exception e) {
      throw new ServiceExecutionException(e);
    }

    return parser.parse(result, dataType);
  }
  
}
